/* Copyright (C) 2010 Fredrique Samuels, fredriquesamuels@gmail.com 

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
//Include library header
#include "md2model.h"

namespace tri{

vec3_t TMD2Model::_kAnorms[] = {};

// Magic number (should be 844121161)
int TMD2Model::_kMd2Ident = 'I' + ('D'<<8) + ('P'<<16) + ('2'<<24);

// MD2 format version
int TMD2Model::_kMd2Version = 8;

TMD2Model::TMD2Model() 
    : tri::TEntity(),
     _skins (NULL), _texCoords (NULL), _triangles (NULL), _frames (NULL),
        _glcmds (NULL), _scale (1.0f), _tex (NULL), m_CurrentFrame(0),
        m_NextFrame(0),m_FrameProgress(0)
{
	mTimer.setMode(tri::TIMER_TYPE);
}

TMD2Model::~TMD2Model(){
	clear();
}

int TMD2Model::load(std::string filename){
      clear();
	  // Open the file
	  std::ifstream ifs (filename.c_str (), std::ios::binary);
	
	  if (ifs.fail ()){
	    return -1;
	  }
	
	  // Read header
	  ifs.read (reinterpret_cast<char *>(&_header), sizeof (Md2Header_t));
	
	  // Check if ident and version are valid
	  if (_header.ident != _kMd2Ident){return -2;}
	
	  if (_header.version != _kMd2Version){return -3;}
	
	  // Memory allocation for model data
	  _skins = new Md2Skin_t[_header.num_skins];
	  _texCoords = new Md2TexCoord_t[_header.num_st];
	  _triangles = new Md2Triangle_t[_header.num_tris];
	  _frames = new Md2Frame_t[_header.num_frames];
	  _glcmds = new int[_header.num_glcmds];
	
	  // Read skin names
	  ifs.seekg (_header.offset_skins, std::ios::beg);
	  ifs.read (reinterpret_cast<char *>(_skins),
		    sizeof (Md2Skin_t) * _header.num_skins);
	
	  // Read texture coords.
	  ifs.seekg (_header.offset_st, std::ios::beg);
	  ifs.read (reinterpret_cast<char *>(_texCoords),
		    sizeof (Md2TexCoord_t) * _header.num_st);
	
	  // Read triangle data
	  ifs.seekg (_header.offset_tris, std::ios::beg);
	  ifs.read (reinterpret_cast<char *>(_triangles),
		    sizeof(Md2Triangle_t) * _header.num_tris);
	
	  // Read frames
	  ifs.seekg (_header.offset_frames, std::ios::beg);
	  for (int i = 0; i < _header.num_frames; ++i)
	    {
	      // Memory allocation for the vertices of this frame
	      _frames[i].verts = new Md2Vertex_t[_header.num_vertices];
	
	      // Read frame data
	      ifs.read (reinterpret_cast<char *>(&_frames[i].scale),
			sizeof (vec3_t));
	      ifs.read (reinterpret_cast<char *>(&_frames[i].translate),
			sizeof (vec3_t));
	      ifs.read (reinterpret_cast<char *>(&_frames[i].name),
			sizeof (char) * 16);
	      ifs.read (reinterpret_cast<char *>(_frames[i].verts),
			sizeof (Md2Vertex_t) * _header.num_vertices);
	    }
	
	  // Read OpenGL commands
	  ifs.seekg (_header.offset_glcmds, std::ios::beg);
	  ifs.read (reinterpret_cast<char *>(_glcmds),
		    sizeof (int) * _header.num_glcmds);
	
	  // Close file
	  ifs.close();
	
	  // Setup animation infos
	  setupAnimations ();
	  
	  return 0;
}

void TMD2Model::setupAnimations ()
{
  string currentAnim;
  Md2Anim_t animInfo = { 0, 0 };

  for (int i = 0; i < _header.num_frames; ++i)
    {
      string frameName = _frames[i].name;
      string frameAnim;

      // Extract animation name from frame name
      string::size_type len = frameName.find_first_of ("0123456789");
      if ((len == frameName.length () - 3) &&
	  (frameName[len] != '0'))
	  len++;
      //std::cout << frameName << std::endl;
      frameAnim.assign (frameName, 0, len);

      if (currentAnim != frameAnim)
	  {
	  if (i > 0)
	    {
	      // Previous animation is finished, insert
	      // it and start new animation.
	      _anims.insert (AnimMap::value_type
			     (currentAnim, animInfo));
	    }

	  // Initialize new anim info
	  animInfo.start = i;
	  animInfo.end = i;

	  currentAnim = frameAnim;
	}
      else
	{
	  animInfo.end = i;
	}
    }

    // Insert last animation
    _anims.insert (AnimMap::value_type (currentAnim, animInfo));
}

void TMD2Model::getAnimationList(std::vector<std::string>& list ){
	list.clear();
	AnimMap::iterator it;
	for ( it=_anims.begin() ; it != _anims.end(); it++ ){
		list.push_back((*it).first);
	}
}

bool TMD2Model::hasAnimation(std::string ani){
	AnimMap::iterator it;
	for ( it=_anims.begin() ; it != _anims.end(); it++ ){
		if((*it).first == ani){
			return true;
		}
	}
	
	return false;
}

TMd2AnimInfo TMD2Model::getAnimationInfo(std::string anim){
	TMd2AnimInfo info;
	info.start=0;
	info.end=0;
	
	AnimMap::iterator it;
	for ( it=_anims.begin() ; it != _anims.end(); it++ ){
		if ((*it).first == anim){
			info.start=(*it).second.start;
			info.end=(*it).second.end;
			return info;
		}
	}
	
	return info;
}

// --------------------------------------------------------------------------
// Md2Model::renderFrameImmediate
//
// Render the model for the specified frame, using immediate mode.
// --------------------------------------------------------------------------

void
TMD2Model::renderFrameImmediate (int frame)
{
  // Compute max frame index
  int maxFrame = _header.num_frames - 1;

  // Check if the frame index is valid
  if ((frame < 0) || (frame > maxFrame))
    return;

  // Bind to model's texture
  tri::applyTexture(getImage());
  
  glBegin (GL_TRIANGLES);
    // Draw each triangle
    for (int i = 0; i < _header.num_tris; ++i)
      {
	// Draw each vertex of this triangle
	for (int j = 0; j < 3; ++j)
	  {
	    Md2Frame_t *pFrame = &_frames[frame];
	    Md2Vertex_t *pVert = &pFrame->verts[_triangles[i].vertex[j]];
	    Md2TexCoord_t *pTexCoords = &_texCoords[_triangles[i].st[j]];

	    // Compute final texture coords.
	    GLfloat s = static_cast<GLfloat>(pTexCoords->s) / _header.skinwidth;
	    GLfloat t = static_cast<GLfloat>(pTexCoords->t) / _header.skinheight;

	    glTexCoord2f (s, 1.0f - t);

	    // Send normal vector to OpenGL
	    //glNormal3fv (_kAnorms[pVert->normalIndex]);

	    // Uncompress vertex position and scale it
	    vec3_t v;

	    v[0] = (pFrame->scale[0] * pVert->v[0] + pFrame->translate[0]) * _scale;
	    v[1] = (pFrame->scale[1] * pVert->v[1] + pFrame->translate[1]) * _scale;
	    v[2] = (pFrame->scale[2] * pVert->v[2] + pFrame->translate[2]) * _scale;

	    glVertex3fv (v);
	  }
      }
  glEnd();
}


// --------------------------------------------------------------------------
// Md2Model::drawModelItpImmediate
//
// Render the model with frame interpolation, using immediate mode.
// --------------------------------------------------------------------------

void
TMD2Model::drawModelItpImmediate (int frameA, int frameB, float interp)
{
  // Compute max frame index
  int maxFrame = _header.num_frames - 1;

  // Check if frames are valid
  if ((frameA < 0) || (frameB < 0))
    return;

  if ((frameA > maxFrame) || (frameB > maxFrame))
    return;

  // Bind to model's texture
  tri::applyTexture(getImage());

  glBegin (GL_TRIANGLES);
    // Draw each triangle
    for (int i = 0; i < _header.num_tris; ++i)
      {
	// Draw each vertex of this triangle
	for (int j = 0; j < 3; ++j)
	  {
	    Md2Frame_t *pFrameA = &_frames[frameA];
	    Md2Frame_t *pFrameB = &_frames[frameB];

	    Md2Vertex_t *pVertA = &pFrameA->verts[_triangles[i].vertex[j]];
	    Md2Vertex_t *pVertB = &pFrameB->verts[_triangles[i].vertex[j]];

	    Md2TexCoord_t *pTexCoords = &_texCoords[_triangles[i].st[j]];

	    // Compute final texture coords.
	    GLfloat s = static_cast<GLfloat>(pTexCoords->s) / _header.skinwidth;
	    GLfloat t = static_cast<GLfloat>(pTexCoords->t) / _header.skinheight;

	    glTexCoord2f (s, t);

	    // Compute interpolated normal vector
//	    const GLfloat *normA = _kAnorms[pVertA->normalIndex];
//	    const GLfloat *normB = _kAnorms[pVertB->normalIndex];
//
//	    vec3_t n;
//	    n[0] = normA[0] + interp * (normB[0] - normA[0]);
//	    n[1] = normA[1] + interp * (normB[1] - normA[1]);
//	    n[2] = normA[2] + interp * (normB[2] - normA[2]);
//
//	    glNormal3fv (n);

	    // Compute final vertex position
	    vec3_t vecA, vecB, v;

	    // First, uncompress vertex positions
	    vecA[0] = pFrameA->scale[0] * pVertA->v[0] + pFrameA->translate[0];
	    vecA[1] = pFrameA->scale[1] * pVertA->v[1] + pFrameA->translate[1];
	    vecA[2] = pFrameA->scale[2] * pVertA->v[2] + pFrameA->translate[2];

	    vecB[0] = pFrameB->scale[0] * pVertB->v[0] + pFrameB->translate[0];
	    vecB[1] = pFrameB->scale[1] * pVertB->v[1] + pFrameB->translate[1];
	    vecB[2] = pFrameB->scale[2] * pVertB->v[2] + pFrameB->translate[2];

	    // Linear interpolation and scaling
	    v[0] = (vecA[0] + interp * (vecB[0] - vecA[0])) * _scale;
	    v[1] = (vecA[1] + interp * (vecB[1] - vecA[1])) * _scale;
	    v[2] = (vecA[2] + interp * (vecB[2] - vecA[2])) * _scale;

	    glVertex3fv (v);
	  }
      }
  glEnd();
}


// --------------------------------------------------------------------------
// Md2Model::renderFrameWithGLcmds
//
// Render the model for the specified frame, using OpenGL commands.
// --------------------------------------------------------------------------

void
TMD2Model::renderFrameWithGLcmds (int frame)
{
  // Compute max frame index
  int maxFrame = _header.num_frames - 1;

  // Check if the frame index is valid
  if ((frame < 0) || (frame > maxFrame))
    return;

  // Bind to model's texture
  tri::applyTexture(getImage());

  // Pointer to OpenGL commands
  int *pGlcmds = _glcmds;
  int i;

  while ((i = *(pGlcmds++)) != 0)
    {
      if (i < 0)
	{
	  glBegin (GL_TRIANGLE_FAN);
	  i = -i;
	}
      else
	{
	  glBegin (GL_TRIANGLE_STRIP);
	}

      // Parse all OpenGL commands of this group
      for (/* nothing */; i > 0; --i, pGlcmds += 3)
	{
	  // pGlcmds[0] : final S texture coord.
	  // pGlcmds[1] : final T texture coord.
	  // pGlcmds[2] : vertex index to draw

	  Md2Glcmd_t *pGLcmd = reinterpret_cast<Md2Glcmd_t *>(pGlcmds);
	  Md2Frame_t *pFrame = &_frames[frame];
	  Md2Vertex_t *pVert = &pFrame->verts[pGLcmd->index];

	  // Send texture coords. to OpenGL
	  glTexCoord2f (pGLcmd->s, 1.0f - pGLcmd->t);

	  // Send normal vector to OpenGL
	  //glNormal3fv (_kAnorms[pVert->normalIndex]);

	  // Uncompress vertex position and scale it
	  vec3_t v;

	  v[0] = (pFrame->scale[0] * pVert->v[0] + pFrame->translate[0]) * _scale;
	  v[1] = (pFrame->scale[1] * pVert->v[1] + pFrame->translate[1]) * _scale;
	  v[2] = (pFrame->scale[2] * pVert->v[2] + pFrame->translate[2]) * _scale;

	  glVertex3fv (v);
	}

      glEnd();
    }
}


// --------------------------------------------------------------------------
// Md2Model::drawModelItpWithGLcmds
//
// Render the model with frame interpolation, using OpenGL commands.
// --------------------------------------------------------------------------

void TMD2Model::drawModelItpWithGLcmds (int frameA, int frameB, float interp)
{
  // Compute max frame index
  int maxFrame = _header.num_frames - 1;

  // Check if frames are valid
  if ((frameA < 0) || (frameB < 0))
    return;

  if ((frameA > maxFrame) || (frameB > maxFrame))
    return;

  tri::applyTexture(getImage());

  // Pointer to OpenGL commands
  int *pGlcmds = _glcmds;
  int i;

  while ((i = *(pGlcmds++)) != 0)
    {
      if (i < 0)
	{
	  glBegin (GL_TRIANGLE_FAN);
	  i = -i;
	}
      else
	{
	  glBegin (GL_TRIANGLE_STRIP);
	}

      // Parse all OpenGL commands of this group
      for (/* nothing */; i > 0; --i, pGlcmds += 3)
	{
	  // pGlcmds[0] : final S texture coord.
	  // pGlcmds[1] : final T texture coord.
	  // pGlcmds[2] : vertex index to draw

	  Md2Glcmd_t *pGLcmd = reinterpret_cast<Md2Glcmd_t *>(pGlcmds);

	  Md2Frame_t *pFrameA = &_frames[frameA];
	  Md2Frame_t *pFrameB = &_frames[frameB];

	  Md2Vertex_t *pVertA = &pFrameA->verts[pGLcmd->index];
	  Md2Vertex_t *pVertB = &pFrameB->verts[pGLcmd->index];

	  // Send texture coords. to OpenGL
	  glTexCoord2f (pGLcmd->s,1.0- pGLcmd->t);

//	  // Compute interpolated normal vector
//	  const GLfloat *normA = _kAnorms [pVertA->normalIndex];
//	  const GLfloat *normB = _kAnorms [pVertB->normalIndex];
//
//	  vec3_t n;
//	  n[0] = normA[0] + interp * (normB[0] - normA[0]);
//	  n[1] = normA[1] + interp * (normB[1] - normA[1]);
//	  n[2] = normA[2] + interp * (normB[2] - normA[2]);
//
//	  glNormal3fv (n);

	  // Compute final vertex position
	  vec3_t vecA, vecB, v;

	  // First, uncompress vertiex positions
	  vecA[0] = pFrameA->scale[0] * pVertA->v[0] + pFrameA->translate[0];
	  vecA[1] = pFrameA->scale[1] * pVertA->v[1] + pFrameA->translate[1];
	  vecA[2] = pFrameA->scale[2] * pVertA->v[2] + pFrameA->translate[2];

	  vecB[0] = pFrameB->scale[0] * pVertB->v[0] + pFrameB->translate[0];
	  vecB[1] = pFrameB->scale[1] * pVertB->v[1] + pFrameB->translate[1];
	  vecB[2] = pFrameB->scale[2] * pVertB->v[2] + pFrameB->translate[2];

	  // Linear interpolation and scaling
	  v[0] = (vecA[0] + interp * (vecB[0] - vecA[0])) * _scale;
	  v[1] = (vecA[1] + interp * (vecB[1] - vecA[1])) * _scale;
	  v[2] = (vecA[2] + interp * (vecB[2] - vecA[2])) * _scale;

	  glVertex3fv (v);
	}

      glEnd();
    }
}


void TMD2Model::display(){
	glCullFace(GL_FRONT);
	glPushMatrix();
	graphics::translate(mOffsetPosition);
	graphics::rotate(mOffsetRotation);
	
	if (m_CurrentAnimation==""){
		renderFrameWithGLcmds(0);
	}else{
		drawModelItpImmediate(m_CurrentFrame,m_NextFrame,m_FrameProgress);
	}
	
	glPopMatrix();
	glCullFace(GL_BACK);
}

void TMD2Model::update(){
	tri::TEntity::update();
	if (mTimer.getProgress()==1.0){
		mTimer.reset();
		tri::TMd2AnimInfo info = getAnimationInfo(m_CurrentAnimation);
		
		if(m_CurrentFrame==info.end){
			m_CurrentFrame = m_LoopAnimation ? info.start : m_CurrentFrame;
			if(!m_LoopAnimation){
				mAnimating = false;
			}
		}else{
			m_CurrentFrame+=1;
		}
		
		if(m_NextFrame==info.end){
			m_NextFrame = m_LoopAnimation ? info.start : m_NextFrame;
		}else{
			m_NextFrame+=1;
		}
	
		m_FrameProgress = 0 ;

	}else{
		m_FrameProgress = mTimer.getProgress();
	}
}

void TMD2Model::setAnimation(std::string ani, float fps, bool loop){
	if (hasAnimation(ani)){
		mTimer.stop();
		tri::TMd2AnimInfo info = getAnimationInfo(ani);
		m_CurrentAnimation = ani;
		m_CurrentFrame = info.start;
		m_NextFrame = info.start!=info.end ? info.start+1 : info.end;
		m_FrameProgress = 0;
		mTimer.setDurationTime(1.0/fps);
		mTimer.start();
		m_LoopAnimation = loop;
		mAnimating = true;
	}
	
	else{
		m_CurrentAnimation = "";
	}
	
}

std::string TMD2Model::getAnimation(){
	return m_CurrentAnimation;
}


void TMD2Model::setOffsetPosition(tri::TVector pos){
	mOffsetPosition = pos;
}

void TMD2Model::setOffsetRotation(tri::TVector rot){
	mOffsetRotation = rot;
}

void TMD2Model::clear(){
	delete [] _skins;
	delete [] _texCoords;
	delete [] _triangles;
	delete [] _frames;
	delete _glcmds;
	delete _tex;
	_anims.clear();
}

}
